#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "GLrender.h"
#include <glog/logging.h>
#include "ShaderSource.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <chrono>

const int gridSize = 32;

const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;

void GLrender::initOpenGL() {
    glewExperimental = GL_TRUE;

    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    wwindow = glfwCreateWindow(
            SCREEN_WIDTH, SCREEN_HEIGHT, "FUCK ME!", nullptr, nullptr);
    glfwMakeContextCurrent(wwindow);

    // glew init and GenBuffers must be put after the window creation.
    glewInit();
//    GLuint vertexBuffer=0;
    // draw only lines
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    glGenBuffers(1, &vbo); // Generate 1 buffer
    glBindBuffer(GL_ARRAY_BUFFER, vbo);

    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);

    sp.Init();
    sp.addShaderFromFile("vertex.glsl", GL_VERTEX_SHADER);
    sp.addShaderFromFile("fragment.glsl", GL_FRAGMENT_SHADER);
    glBindFragDataLocation(sp,0,"outColor");
    glLinkProgram(sp);
    glUseProgram(sp);

    uniView = glGetUniformLocation(sp, "view");
    glm::mat4 view = glm::lookAt(
            glm::vec3(3.2f, 3.2f, 0.0f),
            glm::vec3(0.0f, 0.0f, 0.0f),
            glm::vec3(0.0f, 0.0f, 1.0f)
    );
    glUniformMatrix4fv(uniView, 1, GL_FALSE, glm::value_ptr(view));

    uniProj = glGetUniformLocation(sp, "proj");
    glm::mat4 proj = glm::perspective(
        glm::radians(45.0f), 800.0f / 600.0f, 1.0f, 10.0f);
    glUniformMatrix4fv(uniProj, 1, GL_FALSE, glm::value_ptr(proj));

    uniModel = glGetUniformLocation(sp, "model");
    glm::mat4 trans(1);
    trans = glm::rotate(trans, glm::radians(0.0f), glm::vec3(0.0f, 0.0f, 1.0f));
    glUniformMatrix4fv(uniModel, 1, GL_FALSE, glm::value_ptr(trans));
    // glEnable(GL_DEPTH_TEST);
    // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    GLuint err = glGetError();
    if(err != 0){
        printf("ERROR FUCK! %d\n", err);
        char buffer[512];
        glGetShaderInfoLog(err, 512, nullptr, buffer);
        printf("Compile log:%s\n", buffer);
    } else {
        printf("good\n");
    }

}


void GLrender::addObject(Object *object) {
    objects.push_back(object);
}

void GLrender::update(){
    // actually draw the whole picture
    // bind vertex and indexs

}

void GLrender::mainLoop(){
    LOG(INFO) << "INIT";
    auto t_start = std::chrono::high_resolution_clock::now();

    vector<ShaderElement>vertices;
    vector<int> index;
    for (auto &object : objects) {
        int offert = static_cast<int>(vertices.size());
        auto p = object->triangulate();
        vertices.insert(vertices.end(), 
            p.first.begin(),
            p.first.end());
        for (int &j : p.second) {
            index.push_back(offert + j);
        }
    }
    glBufferData(GL_ARRAY_BUFFER, 
        sizeof(ShaderElement) * vertices.size(),
        &(vertices[0]), GL_STATIC_DRAW);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,
                 sizeof(int) * index.size(),
                 &index[0], GL_STATIC_DRAW);

    GLint posAttrib = glGetAttribLocation(sp, "position");
    glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE,
            sizeof(ShaderElement), 0);

    glEnableVertexAttribArray(posAttrib);



    float alpha=0, delta=0;
    while (!glfwWindowShouldClose(wwindow)){

        glEnable(GL_DEPTH_TEST);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glm::mat4 view = glm::lookAt(
            glm::vec3(
                3 * cos(alpha)*cos(delta),
                3 * sin(alpha) * cos(delta),
                3 * sin(delta)),
            glm::vec3(0.0f, 0.0f, 0.0f),
            glm::vec3(0.0f, 0.0f, 1.0f)
        );
        glUniformMatrix4fv(uniView, 1, GL_FALSE, glm::value_ptr(view));

        auto t_now = std::chrono::high_resolution_clock::now();
        float time = std::chrono::duration_cast<std::chrono::duration<float> >
                (t_now - t_start).count();
        // use draw arrays here instead of draw elements
        glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(index.size()), GL_UNSIGNED_INT, nullptr);
        glEnable(GL_PROGRAM_POINT_SIZE);
        // if esc key is pressed, then exit
        if(glfwGetKey(wwindow, GLFW_KEY_ESCAPE) == GLFW_PRESS){
            glfwSetWindowShouldClose(wwindow, true);
        }
        if(glfwGetKey(wwindow, GLFW_KEY_LEFT) == GLFW_PRESS){
            alpha += 0.03;
        }
        if(glfwGetKey(wwindow, GLFW_KEY_RIGHT) == GLFW_PRESS){
            alpha -= 0.03;
        }
        if(glfwGetKey(wwindow, GLFW_KEY_UP) == GLFW_PRESS){
            delta += 0.03;
        }
        if(glfwGetKey(wwindow, GLFW_KEY_DOWN) == GLFW_PRESS){
            delta -= 0.03;
        }
        // swap buffers, and show the newly drown things.
        glfwSwapBuffers(wwindow);
        // swallow the not handled events.
        glfwPollEvents();
    }
    glfwTerminate();
}